using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.Windows.Forms;

namespace DMTT.Workflow.Activities.Design
{
	public class ConnectionDrawingService
	{
		
		#region Private Variables
		
        private const int INDENT_WIDTH = 20;
        private const int LINE_WIDTH_TOP = 1;
        private const int LINE_WIDTH_INNER = 2;
		
		private LinkItem m_dragDropLink = null;
        private TreeViewLinkItemCollection m_treeViewLinkItems;
        private TreeViewLinkItem m_selectedTreeViewItem;
		private Color m_colorTop;
		private Color m_colorInner;
		private Color m_colorBottom;
		private Color m_colorSelection;
		
		#endregion
		
		
		#region Constructors
		
		public ConnectionDrawingService()
		{
            m_treeViewLinkItems = new TreeViewLinkItemCollection();
            m_treeViewLinkItems.TreeViewLinkDeleted += delegate(object sender, TreeViewLinkDeletedEventArgs e)
            {
                this.OnTreeViewLinkDeleted(e);
            };

            m_selectedTreeViewItem = null;
			m_colorTop = Color.White;
			m_colorInner = Color.LightGray;
            m_colorBottom = Color.Black;
            m_colorSelection = Color.DarkBlue;
		}
		
		#endregion
		
		
		#region Public Properties

        public TreeViewLinkItemCollection TreeViewLinkItems
        {
            get { return m_treeViewLinkItems; }
        }

        public TreeViewLinkItem SelectedTreeViewItem
        {
            get { return m_selectedTreeViewItem; }
            set { m_selectedTreeViewItem = value; }
        }
		
		public Color ColorTop
		{
			get{return m_colorTop;}
			set{m_colorTop = value;}
		}
		
		public Color ColorInner
		{
			get{return m_colorInner;}
			set{m_colorInner = value;}
		}
		
		public Color ColorBottom
		{
			get{return m_colorBottom;}
			set{m_colorBottom = value;}
		}
		
		public Color ColorSelection
		{
			get{return m_colorSelection;}
			set{m_colorSelection = value;}
		}
		
		#endregion


        #region Public Events

        public event TreeViewLinkDeletedEventHandler TreeViewLinkDeleted;

        private void OnTreeViewLinkDeleted(TreeViewLinkDeletedEventArgs e)
        {
            if (TreeViewLinkDeleted != null)
            {
                TreeViewLinkDeleted(this, e);
            }
        }

        #endregion


        #region Private Functions

		private System.Drawing.Pen CreateLinkPenTop()
		{
			return new Pen(this.ColorTop, LINE_WIDTH_TOP);
		}
		
		private System.Drawing.Pen CreateLinkPenInner()
		{
			return new Pen(this.ColorInner, LINE_WIDTH_INNER);
		}
		
		private System.Drawing.Pen CreateLinkPenBottom()
		{
			return new Pen(this.ColorBottom, 1);
		}
		
		private System.Drawing.Pen CreateLinkOutlinePen()
		{
			return new Pen(this.ColorSelection, LINE_WIDTH_INNER);
		}
		
		private void CreateDragLinkPath(
			Point startPoint,
			Point endPoint,
			out GraphicsPath gpTop,
			out GraphicsPath gpInner,
			out GraphicsPath gpBottom)
		{
			this.CreateLineDragLinkPath(
				startPoint, 
				endPoint, 
				out gpTop, 
				out gpInner, 
				out gpBottom);
		}
		
		private void CreateLineDragLinkPath(
			Point startPoint,
			Point endPoint,
			out GraphicsPath gpTop,
			out GraphicsPath gpInner,
			out GraphicsPath gpBottom)
		{
			Point[] pointsTop = new Point[]{};
			Point[] pointsInner = new Point[]{};
			Point[] pointsBottom = new Point[]{};
			
			int halfWidthInner = LINE_WIDTH_INNER/2;
			int halfWidthInnerTop = halfWidthInner -1;
            int halfWidthInnerBottom = halfWidthInner;
			
			if (startPoint.Y <= endPoint.Y)
			{
				pointsTop = new Point[4]{
											new Point(startPoint.X, startPoint.Y - halfWidthInnerTop),
											new Point(startPoint.X + halfWidthInnerTop + INDENT_WIDTH, startPoint.Y - halfWidthInnerTop),
											new Point(endPoint.X + halfWidthInnerTop - INDENT_WIDTH, endPoint.Y - halfWidthInnerTop),
											new Point(endPoint.X, endPoint.Y - halfWidthInnerTop)
										};
				pointsInner = new Point[4]{
											  startPoint,
											  new Point(startPoint.X + INDENT_WIDTH, startPoint.Y),
											  new Point(endPoint.X - INDENT_WIDTH, endPoint.Y),
											  endPoint
										  };
				pointsBottom = new Point[4]{
											   new Point(startPoint.X, startPoint.Y + halfWidthInnerBottom),
											   new Point(startPoint.X - halfWidthInnerBottom + INDENT_WIDTH, startPoint.Y + halfWidthInnerBottom),
											   new Point(endPoint.X - halfWidthInnerBottom - INDENT_WIDTH, endPoint.Y + halfWidthInnerBottom),
											   new Point(endPoint.X, endPoint.Y + halfWidthInnerBottom)
										   };
			}
			else
			{
				pointsTop = new Point[4]{
											new Point(startPoint.X, startPoint.Y - halfWidthInnerTop),
											new Point(startPoint.X - halfWidthInnerTop + INDENT_WIDTH, startPoint.Y - halfWidthInnerTop),
											new Point(endPoint.X - LINE_WIDTH_TOP - INDENT_WIDTH, endPoint.Y - halfWidthInnerTop),
											new Point(endPoint.X, endPoint.Y - halfWidthInnerTop)
										};
				pointsInner = new Point[4]{
											  startPoint,
											  new Point(startPoint.X + INDENT_WIDTH, startPoint.Y),
											  new Point(endPoint.X - INDENT_WIDTH, endPoint.Y),
											  endPoint
										  };
				pointsBottom = new Point[4]{
											   new Point(startPoint.X, startPoint.Y + halfWidthInnerBottom),
											   new Point(startPoint.X + halfWidthInnerBottom + INDENT_WIDTH, startPoint.Y + halfWidthInnerBottom),
											   new Point(endPoint.X + halfWidthInnerBottom - INDENT_WIDTH, endPoint.Y + halfWidthInnerBottom),
											   new Point(endPoint.X, endPoint.Y + halfWidthInnerBottom)
										   };
			}
			
			byte[] types = new byte[4]{
										  (byte)PathPointType.Start,
										  (byte)PathPointType.Line,
										  (byte)PathPointType.Line,
										  (byte)PathPointType.Line
									  };
			
			gpTop =  new GraphicsPath(pointsTop, types);
			gpInner =  new GraphicsPath(pointsInner, types);
			gpBottom =  new GraphicsPath(pointsBottom, types);
		}
		
		private void DrawSelectedItem(Graphics graphics)
		{
			if (graphics == null) throw new ArgumentNullException("graphics");
            if (this.SelectedTreeViewItem != null)
            {
                graphics.DrawPath(this.CreateLinkOutlinePen(), this.SelectedTreeViewItem.PathInner);
                graphics.DrawPath(new Pen(this.ColorSelection, LINE_WIDTH_TOP), this.SelectedTreeViewItem.PathTop);
                graphics.DrawPath(this.CreateLinkPenBottom(), this.SelectedTreeViewItem.PathBottom);
            }
        }
		
		private void DrawDragLink(Graphics graphics)
		{
			if (graphics == null) throw new ArgumentNullException("graphics");
			if (m_dragDropLink != null)
			{
				Pen pn = new Pen(Color.DarkGray, 1);
				pn.Width = 1;
				pn.DashStyle = DashStyle.Dash;
				graphics.DrawPath(pn, m_dragDropLink.PathInner);
				graphics.DrawPath(pn, m_dragDropLink.PathTop);
				graphics.DrawPath(pn, m_dragDropLink.PathBottom);
				pn.Dispose();
			}
		}
		
		#endregion
		
		
		#region Public Functions

        public LinkItem AddLinkItem(
            TreeView startTreeView,
            TreeView endTreeView)
        {
            if (startTreeView == null) throw new ArgumentNullException("startTreeView");
            if (endTreeView == null) throw new ArgumentNullException("endTreeView");

            if (startTreeView.SelectedNode == null) throw new ArgumentException("No nodes on start TreeView selected!");
            if (endTreeView.SelectedNode == null) throw new ArgumentException("No nodes on end TreeView selected!");

            return m_treeViewLinkItems.Add(
                startTreeView.SelectedNode,
                endTreeView.SelectedNode);
        }

        public LinkItem AddLinkItem(
            TreeNode sourceNode,
            TreeNode destNode)
        {
            if (sourceNode == null) throw new ArgumentNullException("sourceNode");
            if (destNode == null) throw new ArgumentNullException("destNode");

            return m_treeViewLinkItems.Add(
                sourceNode,
                destNode);
        }
		
		public LinkItem CreateDragLink(
			Point startPoint,
			Point endPoint)
		{
			GraphicsPath gpTop;
			GraphicsPath gpInner;
			GraphicsPath gpBottom;
			
			this.CreateDragLinkPath(startPoint, endPoint, out gpTop, out gpInner, out gpBottom);
			
			m_dragDropLink = new LinkItem(startPoint, endPoint, gpTop, gpInner, gpBottom);
			
			return m_dragDropLink;
		}
		
		public void DestroyDragLink()
		{
			m_dragDropLink = null;
		}
		
		public void RedrawItems(System.Drawing.Graphics graphics)
		{
			if (graphics == null) throw new ArgumentNullException("graphics");

            if (this.TreeViewLinkItems != null &&
				this.TreeViewLinkItems.Count > 0)
            {
                foreach (LinkItem item in this.TreeViewLinkItems)
                {
                    graphics.DrawPath(this.CreateLinkPenInner(), item.PathInner);
                    graphics.DrawPath(this.CreateLinkPenTop(), item.PathTop);
                    graphics.DrawPath(this.CreateLinkPenBottom(), item.PathBottom);
                }
				
				this.DrawSelectedItem(graphics);
			}
			
			this.DrawDragLink(graphics);
		}

        public void RemoveTreeViewItem(TreeViewLinkItem item)
        {
            if (item == null) throw new ArgumentNullException("item");

            bool isSelectedItem = (item == this.SelectedTreeViewItem);
            m_treeViewLinkItems.Remove(item);
            Application.DoEvents();
            foreach (TreeViewLinkItem tvItem in m_treeViewLinkItems)
            {
                tvItem.Refresh();
            }
            if (isSelectedItem)
                this.SelectedTreeViewItem = null;
            Application.DoEvents();
        }
		
        public void SelectTreeViewItemAt(
            System.Drawing.Graphics graphics,
            Point hitPoint)
        {
            if (graphics == null) throw new ArgumentNullException("graphics");

            this.SelectedTreeViewItem = this.GetTreeViewItemAt(graphics, hitPoint) as TreeViewLinkItem;

            this.RedrawItems(graphics);
        }

        public LinkItem GetTreeViewItemAt(
            System.Drawing.Graphics graphics,
            Point hitPoint)
        {
            if (graphics == null) throw new ArgumentNullException("graphics");

            Pen pn = this.CreateLinkPenInner();

            foreach (LinkItem item in m_treeViewLinkItems)
            {
                if (item.PathTop.IsOutlineVisible(hitPoint, pn, graphics) ||
                    item.PathInner.IsOutlineVisible(hitPoint, pn, graphics) ||
                    item.PathBottom.IsOutlineVisible(hitPoint, pn, graphics))
                    return item;
            }
            return null;
        }

        public Point GetTreeNodePos(
            TreeView tree,
            bool start)
        {
            int offset = (start) ? tree.Width : 0;
            return new Point(
                tree.Location.X + offset,
                tree.Location.Y + tree.SelectedNode.Bounds.Location.Y +
                (tree.SelectedNode.Bounds.Height / 2) + 2);
        }

        public bool HitTestTreeView(
            System.Drawing.Graphics graphics,
            Point hitPoint)
        {
            if (graphics == null) throw new ArgumentNullException("graphics");
            return this.GetTreeViewItemAt(graphics, hitPoint) != null;
        }
		
		#endregion
		
	}
	
}
